package br.com.bean;

import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.Set;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.ServletContext;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.apache.commons.lang3.text.WordUtils;
import org.primefaces.event.UnselectEvent;

import br.com.domain.Domain;
import br.com.domain.DomainFixed;
import br.com.domain.Perfil;
import br.com.exception.BeanException;
import br.com.exception.ServiceException;
import br.com.filter.Filter;
import br.com.service.GenericServiceImpl;
import br.com.util.DomainListDataModel;
import br.com.util.PropertiesUtil;

@Named
@SessionScoped
@SuppressWarnings("rawtypes")
public class GenericBean implements Serializable {
	
	private static final long serialVersionUID = 1L;
	
	/**************************************************************************/
	/**                                ATRIBUTOS                             **/
	/**************************************************************************/

	@Inject
	private SessionBean sessionBean;
	
	/** Lista de Objetos Generica Selecionados **/
	private DomainListDataModel listDomain;
	
	/** Lista de Objetos Generica Selecionados **/
	private List<? extends Domain> listDomainSelecionados;
	
	/** Domain Generico **/
	private Domain domain;
	
	/** String de Busca **/
	private String buscarTexto;
	
	/** Flag de Escrita **/
	private Boolean isOnlyRead;
	
	/** Flag de Novo Registro **/
	private Boolean isNewRecord;
	
	/** Flag de Exclus�o de Registro **/
	private Boolean isDeleteRecord;
	
	/** Flag de Exclus�o de Registro **/
	private Boolean isEditRecord;
	
	/** Flag de Visualizacao dos Filtros **/
	private Boolean isFilterVisible = false;
	
	/** Flag de Visualizacao dos Filtros **/
	private Boolean isButtonDeleteDisabled = true;
	
	/** Flag de Dominios Fixos  **/
	private Boolean isDomainFixed = false;
	
	/** Generic Service **/
	private GenericServiceImpl genericService;
	
	/**************************************************************************/
	/**                                M�TODOS                               **/
	/**************************************************************************/
	
	/** REDIRECIONA PARA UM P�GINA POR NOME **/
	public void goPage(String page)  {
		try {
			
			getExternalContext().redirect(getExternalContext().encodeResourceURL
					(getExternalContext().getRequestContextPath() +
					     "/pages/"+page+".jsf"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/** RETORNA UM EXTERNAL CONTEXT **/
	public ExternalContext getExternalContext()  {
		ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
		return externalContext;
	}
	
	/** RETORNA UM SERVLET CONTEXT **/
	public ServletContext getServletContext()  {
	    ServletContext servletContext = (ServletContext) getExternalContext().getContext();  
		return servletContext;
	}
	
	/** RETORNA UM FACESCONTEXT  **/
	public FacesContext getFacesContext()  {
		return FacesContext.getCurrentInstance();
	}
	
	
	/** RETORNA PAGINA ATUAL  **/
	public String getPageAtual() {
		String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
		viewId = viewId.replaceAll("/pages/", "");
		viewId = viewId.replaceAll(".xhtml", "");
		return viewId;
	}
	
	/** REFRESH PAGINA ATUAL  **/
	public void refreshPageAtual() {
		String pageAtual = getPageAtual();
		goPage(pageAtual);
	}
	
	/** VALIDAR CLASSES DE DOMINIOS **/  
	public  boolean validarDomain(Domain domain) throws BeanException{
		boolean retorno                             = true;
		StringBuilder listMessage                   = new StringBuilder();
		ValidatorFactory validatorFactory           = Validation.buildDefaultValidatorFactory();
		Validator validator                         = validatorFactory.getValidator();
		Set<ConstraintViolation<Domain>> violations = validator.validate(domain);
		retorno = !(violations.size() > 0); 

		for (ConstraintViolation<Domain> violation : violations) {
			String properties =  violation.getPropertyPath().toString();
			listMessage.append(WordUtils.capitalize(properties) + " - " + violation.getMessage() + "<br />");	
		}

		if (listMessage.length() > 0) {
			throw new BeanException(listMessage.toString());
		}
		return retorno;
	}
	
	/** RETORNA UM MANAGED BEAN POR NOME **/
	@SuppressWarnings("el-syntax")
	public static Object getManagedBean(String beanName) { 
		FacesContext fc      = FacesContext.getCurrentInstance(); 
		ELContext elc        = fc.getELContext(); 
		ExpressionFactory ef = fc.getApplication().getExpressionFactory(); 
		ValueExpression ve   = ef.createValueExpression(elc, "#{" + beanName + "}", Object.class); 
		return ve.getValue(elc); 
	}
	
	
	/** EXIBIR OCULTAR FILTRO DE PESQUISA  **/
	public  void showFilter(){
		this.isFilterVisible = !this.isFilterVisible;
	}
	
	/** MODO  NOVO REGISTRO  **/
	public  void  activeModoNovo(){
		this.resetModos();
		this.isNewRecord = true;	
	}
	
	/** MODO EDITAR REGISTRO  **/
	public  void  activeModoEditar(){
		this.resetModos();
		this.isEditRecord = true;
	}

	/** MODO EXLUIR REGISTRO  **/
	public  void  activeModoExcluir(){
		this.resetModos();
		this.isDeleteRecord = true;	
	}
	
	/** MODO VISUALIZAR REGISTRO  **/
	public  void activeModoVisualizar(){
		this.resetModos();
		this.isOnlyRead = true;
	}
	
	/** MODO EXLUIR REGISTRO  **/
	public  void  resetModos(){
		this.isDeleteRecord = false;
        this.isNewRecord    = false;
        this.isOnlyRead     = false;
        this.isEditRecord   = false;
	}
	
	/**
	 * INSERT/UPDATE MANUAL DO NOVO OBJETO
	 * @return void
	 */
	public void updateList(Domain domain, DomainListDataModel list)  {
	
       if(domain.getId() != null) {
           if (this.isNewRecord) {
        	   list.add(domain, 0);
    	   } 
    	   else
    	   if (this.isDeleteRecord) {
    		   list.remove(domain);
     	   } 
       }
	}
	
	/**
	 * INSERT/UPDATE MANUAL DO NOVO OBJETO
	 * @return void
	 */
	public void updateList(Domain domain, DomainListDataModel list, Integer index)  {
	
       if(domain.getId() != null) {
           if (this.isNewRecord) {
        	   list.add(domain, index);
    	   } 
    	   else
    	   if (this.isDeleteRecord) {
    		   list.add(domain, index);
     	   } 
       }
	}
	
	/**
	 * INSERT/UPDATE MANUAL DO NOVO OBJETO
	 * @return void
	 */
	public void updateList(DomainListDataModel listBean, List<? extends Domain> listSelecionados)  {
	 
		for(Domain domainSelecionado: listSelecionados) {
			this.updateList(domainSelecionado, listBean);
		}
		
		if(this.isDeleteRecord) {
			this.setListDomainSelecionados(null);
		}
	}
	
	
	/** LINHA DES-SELECIONADA **/
	public void onRowUnSelect(UnselectEvent event) {
		getListDomainSelecionados().remove(event.getObject());
	}
	
	/** FUNCAO HABILITA / DESABILITA EXCLUSAO / NAO VALIDA USUARIO **/
	public boolean desabilitarExclusao(DomainListDataModel listBean, List<? extends Domain> listSelecionados) {
		
		if (listBean == null || listBean.size() == 0) {
			return true;
		}
		return listSelecionados == null ? true : !(listSelecionados.size() > 0);
	}
	
	
	/**
	 * SALVAR DOMINIO
	 * @return void
	 * @throws BeanException 
	 */
	public Domain salvar(Domain domain) throws BeanException  {	
	    try {
	        if (!this.validarDomain(domain)){
	        	return null;
	        }
	        
			Domain domainSaved = this.getGenericService().salvar(domain);
			this.updateList(domainSaved, this.getListDomain());
			return domainSaved;
		
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}	
	}
	
	/**
	 * SALVAR DOMINIO
	 * @return void
	 * @throws BeanException 
	 */
	public Domain salvar(Domain domain, Integer index) throws BeanException  {	
	    try {
	        if (!this.validarDomain(domain)){
	        	return null;
	        }
	        
			Domain domainSaved = this.getGenericService().salvar(domain);
			this.updateList(domainSaved, this.getListDomain(), index);
			return domainSaved;
		
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}	
	}
	
	/**
	 * SALVAR LISTAS DE DOMINIO
	 * @return void
	 * @throws BeanException 
	 */
	public void salvar(List<? extends Domain> listDomain) throws BeanException  {	
	    try {	       
			this.getGenericService().salvar(listDomain);
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}	
	}
	
	/**
	 * SALVAR REGISTRO
	 * @return void
	 * @throws BeanException 
	 */
	public void salvarSemValidar(Domain domain) throws BeanException  {	
	    try {
			Domain domainSaved = this.getGenericService().salvar(domain);
			this.updateList(domainSaved, this.getListDomain());
		
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}	
	}
	
	/**
	 * EDITAR REGISTRO
	 * @return void
	 * @throws BeanException 
	 */
	public void editar(Domain domain) throws BeanException  {	
	    try {
	        this.activeModoEditar();
	        domain = this.getGenericService().buscarPorId
	                 (domain.getClass().getSimpleName(), domain.getId());
	    	if (domain == null) {
	    		throw new BeanException
	    		   (PropertiesUtil.getMessageChave("msg-exception-genericbean-editar"));
	    	}
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}	
	}
	
	/**
	 * EDITAR REGISTRO
	 * @return Domain
	 * @throws BeanException 
	 */
	public Domain editar(String nomeClasse, Integer id) throws BeanException  {	
		Domain domain;
	    try {
	        this.activeModoEditar();
	        domain = this.getGenericService().buscarPorId(nomeClasse, id);
	    	if (domain == null) {
	    		throw new BeanException
	    		   (PropertiesUtil.getMessageChave("msg-exception-genericbean-editar"));
	    	}
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}	
	    
	    return domain;
	}
	
	/**
	 * EXCLUIR REGISTROS SELECIONADOS
	 * @return void
	 * @throws BeanException 
	 */
	public void excluir(List<? extends Domain> listDomain) throws BeanException  {
		try {
			this.activeModoExcluir();	
			this.getGenericService().excluir(getListDomainSelecionados());
			this.updateList(this.getListDomain(), listDomain);
		
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}
	}
	
	/**
	 * EXCLUIR REGISTROS SELECIONADOS
	 * @return void
	 * @throws BeanException 
	 */
	public void excluir(Domain domain) throws BeanException  {
		try {
		
			this.activeModoExcluir();	
			this.getGenericService().excluir(domain);
			this.updateList(domain, getListDomain());
		
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}
	}
	
	/**
	 * BUSCAR TODOS OS REGISTROS
	 * @return DomainListDataModel
	 * @throws BeanException 
	 */
	public DomainListDataModel buscarTodos(Class domain, String... orderBy) throws BeanException  {
		try {
			
			this.listDomain  = 
				new DomainListDataModel
					 (this.getGenericService().buscarTodos(domain.getSimpleName(), orderBy)) ;
			this.validarDomainFixed(this.listDomain);
		
		} catch (ServiceException e) {
			throw new BeanException(e.getMessage());
		}
		
		return this.listDomain;
	}
	
	/**
	 * BUSCAR DADOS POR FILTRO
	 * @return void
	 * @throws BeanException 
	 */
	public DomainListDataModel buscarPorFiltro(Filter filter) throws BeanException  {
	   try {
	   
	   		this.listDomain = 
	   			new DomainListDataModel
	   				 (this.getGenericService().buscarPorFilter(filter));
	   		this.validarDomainFixed(this.listDomain);
	   				
	   } catch (ServiceException e) {
		   throw new BeanException(e.getMessage());
	   }
	   return this.listDomain;
	}
	
	/**
	 * NOVO REGISTRO
	 * @return void
	 * @throws BeanException 
	 */
	
	public void novo(Class domain) throws BeanException   {
		try{
		    this.activeModoNovo();
		    domain.newInstance();
		} catch (Exception e) {
			throw new BeanException(e.getMessage());
		}
	}
	
	/**
	 * VALIDAR DOMINIOS FIXOS
	 * @return void
	 * @throws BeanException 
	 */
	public void validarDomainFixed(DomainListDataModel domainListDataModel) throws BeanException   {
		try{
			
		  for(Domain domain: domainListDataModel.getListDomain()) {
			  if (domain instanceof DomainFixed) {
				  this.isDomainFixed = true;
				  break;
			  }
		  }
			
		} catch (Exception e) {
			throw new BeanException(e.getMessage());
		}
	}
	
	/**************************************************************************/
	/**                           GET's E SET's                              **/
	/**************************************************************************/
	
	public SessionBean getSessionBean() {
		return sessionBean;
	}

	public void setSessionBean(SessionBean sessionBean) {
		this.sessionBean = sessionBean;
	}

	public String getBuscarTexto() {
		return buscarTexto;
	}

	public void setBuscarTexto(String buscarTexto) {
		this.buscarTexto = buscarTexto;
	}

	public Boolean getIsOnlyRead() {
		return isOnlyRead == null ? false : isOnlyRead ;
	}

	public void setIsOnlyRead(Boolean isOnlyRead) {
		this.isOnlyRead = isOnlyRead;
	}

	public Boolean getIsNewRecord() {
		return isNewRecord  == null ? false : isNewRecord ;
	}

	public void setIsNewRecord(Boolean isNewRecord) {
		this.isNewRecord = isNewRecord;
	}

	public Boolean getIsDeleteRecord() {
		return isDeleteRecord  == null ? false : isDeleteRecord ;
	}

	public void setIsDeleteRecord(Boolean isDeleteRecord) {
		this.isDeleteRecord = isDeleteRecord;
	}

	public Boolean getIsFilterVisible() {
		return isFilterVisible  == null ? false : isFilterVisible ;
	}

	public void setIsFilterVisible(Boolean isFilterVisible) {
		this.isFilterVisible = isFilterVisible;
	}

	public Boolean getIsButtonDeleteDisabled() {
		return isButtonDeleteDisabled;
	}

	public void setIsButtonDeleteDisabled(Boolean isButtonDeleteDisabled) {
		this.isButtonDeleteDisabled = isButtonDeleteDisabled;
	}

	public List<? extends Domain> getListDomainSelecionados() {
		return listDomainSelecionados;
	}

	public void setListDomainSelecionados(
			List<? extends Domain> listDomainSelecionados) {
		this.listDomainSelecionados = listDomainSelecionados;
	}

	public GenericServiceImpl getGenericService() {
		return genericService;
	}

	public void setGenericService(GenericServiceImpl genericService) {
		this.genericService = genericService;
	}

	public DomainListDataModel getListDomain() {
		return listDomain;
	}

	public void setListDomain(DomainListDataModel listDomain) {
		this.listDomain = listDomain;
	}

	public Domain getDomain() {
		return domain;
	}

	public void setDomain(Domain domain) {
		this.domain = domain;
	}

	public Boolean getIsDomainFixed() {
		return isDomainFixed;
	}

	public void setIsDomainFixed(Boolean isDomainFixed) {
		this.isDomainFixed = isDomainFixed;
	}

	public Boolean getIsEditRecord() {
		return isEditRecord;
	}

	public void setIsEditRecord(Boolean isEditRecord) {
		this.isEditRecord = isEditRecord;
	}

}
